package org.infodavid.common.impl.lang;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import org.infodavid.common.lang.Serializer;
import org.infodavid.common.lang.SerializerContext;

/**
 * The Class SerializerImpl.
 */
public class SerializerImpl extends Serializer {

	/** ALIAS_TYPES . */
	public static final Map<Class<?>,String> ALIAS_TYPES =
		new HashMap<Class<?>,String>();

	/** The logger. */
	private static final Log LOGGER = LogFactory.getLog(SerializerImpl.class);
	/** The converters. */
	public static final transient Map<Class<?>,Converter> CONVERTERS =
		new HashMap<Class<?>,Converter>();

	static {
		ALIAS_TYPES.put(SerializerContext.class, "serializerContext");
	}

	/** the serializer. */
	private final transient XStream xstream;

	/**
	 * The Constructor.
	 * @throws Exception the exception
	 */
	protected SerializerImpl() throws Exception {
		xstream = new XStream();
		xstream.setMode(XStream.XPATH_ABSOLUTE_REFERENCES);

		LOGGER.debug("Initializing aliases");

		// Get mapping classes
		xstream.useAttributeFor(int.class);
		xstream.useAttributeFor(double.class);
		xstream.useAttributeFor(long.class);
		xstream.useAttributeFor(byte.class);
		xstream.useAttributeFor(float.class);
		xstream.useAttributeFor(Number.class);
		xstream.useAttributeFor(Integer.class);
		xstream.useAttributeFor(Double.class);
		xstream.useAttributeFor(Long.class);
		xstream.useAttributeFor(Byte.class);
		xstream.useAttributeFor(Short.class);
		xstream.useAttributeFor(Float.class);
		xstream.useAttributeFor(BigDecimal.class);
		xstream.useAttributeFor(BigInteger.class);
		xstream.useAttributeFor(Date.class);
		xstream.useAttributeFor(Timestamp.class);
		xstream.useAttributeFor(Charset.class);
		xstream.useAttributeFor(Character.class);
		xstream.useAttributeFor(SerializerContext.class, "version");
		xstream.useAttributeFor(SerializerContext.class, "author");

		for (final Entry<Class<?>,String> entry : ALIAS_TYPES.entrySet()) {
			final Class<?> clazz = entry.getKey();

			xstream.aliasType(entry.getValue(), clazz);
			xstream.alias(entry.getValue(), clazz);
		}

		for (final Entry<Class<?>,Converter> entry : CONVERTERS.entrySet()) {
			xstream.registerConverter(entry.getValue());
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.lang.Serializer#deserialize(java.lang.String)
	 */
	@Override
	public Object deserialize(final String content) {
		return xstream.fromXML(content);
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.lang.Serializer#getDelegate()
	 */
	@Override
	public XStream getDelegate() {
		return xstream;
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.lang.Serializer#serialize(java.lang.Object,
	 * java.nio.charset.Charset)
	 */
	@Override
	public String serialize(final Object data, final Charset encoding) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Begin serialize() using data = " + data);
		}

		final StringBuffer buffer = new StringBuffer();

		serialize(data, buffer, encoding);

		return buffer.toString();
	}

	/**
	 * Return the converters.
	 * @return the converters
	 */
	protected Map<Class<?>,Converter> getConverters() {
		return CONVERTERS;
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.lang.Serializer#serialize(java.lang.Object,
	 * java.lang.StringBuffer, java.nio.charset.Charset)
	 */
	@Override
	public void serialize(final Object data, final StringBuffer buffer,
			final Charset encoding) {
		buffer.append("<?xml version='1.0'");

		Charset charset = encoding;

		if (charset == null) {
			charset = Charset.defaultCharset();
		}

		buffer.append(" encoding='");
		buffer.append(charset.name());
		buffer.append('\'');
		buffer.append("?>\n");
		buffer.append(xstream.toXML(data));
	}
}
